﻿#include "ShaderProgram.h"
#include <GLCommon.h>
#include <fstream>
#include <iostream>

#include "Debuger.h"

FShaderProgram::FShaderProgram(const GLuint InProgramHandle) : ProgramHandle(InProgramHandle)
{
}

FShaderProgram::~FShaderProgram()
{
	if (ProgramHandle != 0)
	{
		glDeleteProgram(ProgramHandle);
	}
}

void FShaderProgram::Use() const
{
	glUseProgram(ProgramHandle);
}

void FShaderProgram::SetBool(const std::string& Name, const bool Value) const
{
	const auto Index = glGetUniformLocation(ProgramHandle, Name.c_str());
	EnsureWarning(Index != -1, "无效的 Uniform Name [%hs]", Name.c_str());
	glUniform1i(Index, static_cast<int>(Value));
}

void FShaderProgram::SetInt(const std::string& Name, const int Value) const
{
	const auto Index = glGetUniformLocation(ProgramHandle, Name.c_str());
	EnsureWarning(Index != -1, "无效的 Uniform Name [%hs]", Name.c_str());
	glUniform1i(Index, Value);
}

void FShaderProgram::SetFloat(const std::string& Name, const float Value) const
{
	const auto Index = glGetUniformLocation(ProgramHandle, Name.c_str());
	EnsureWarning(Index != -1, "无效的 Uniform Name [%hs]", Name.c_str());
	glUniform1f(Index, Value);
}

void FShaderProgram::SetMat4(const std::string& Name, const glm::mat4& Mat) const
{
	const auto Index = glGetUniformLocation(ProgramHandle, Name.c_str());
	EnsureWarning(Index != -1, "无效的 Uniform Name [%hs]", Name.c_str());
	glUniformMatrix4fv(Index, 1, GL_FALSE, glm::value_ptr(Mat));
}

void FShaderProgram::SetVec3(const std::string& Name, const glm::vec3& Vec3) const
{
	const auto Index = glGetUniformLocation(ProgramHandle, Name.c_str());
	EnsureWarning(Index != -1, "无效的 Uniform Name [%hs]", Name.c_str());
	glUniform3fv(Index, 1, glm::value_ptr(Vec3));
}

void PrintSharderLog(const uint32_t Handle, const std::string& InfoStr)
{
	static char InfoLogBuffer[1024];
	glGetShaderInfoLog(Handle, 1024, nullptr, InfoLogBuffer);
	std::cout << InfoStr << std::endl;
	std::cout << "LogInfo : \n" << InfoLogBuffer << std::endl;
}

void PrintProgramLog(const GLuint Handle, const std::string& InfoStr)
{
	static char InfoLogBuffer[1024];
	glGetProgramInfoLog(Handle, 1024, nullptr, InfoLogBuffer);
	std::cout << InfoStr << std::endl;
	std::cout << "LogInfo : \n" << InfoLogBuffer << std::endl;
}

std::optional<GLuint> ShaderBuild::CompileShaderFromSrcCode(const EShaderType Type, const GLchar* CodeStr) const
{
	GLuint Handle = glCreateShader(Type == EShaderType::Vertex ? GL_VERTEX_SHADER : GL_FRAGMENT_SHADER);

	// 这里其实是可以指定多个 FShaderProgram 源代码的
	// https://www.khronos.org/registry/OpenGL-Refpages/gl4/
	glShaderSource(Handle, 1, &CodeStr, nullptr);

	glCompileShader(Handle);

	GLint success = false;
	glGetShaderiv(Handle, GL_COMPILE_STATUS, &success);
	if (success != GL_TRUE)
	{
		PrintSharderLog(Handle, "编译 FShaderProgram 错误");

		glDeleteShader(Handle);

		return {};
	}

	return std::make_optional(Handle);
}

std::optional<GLuint> ShaderBuild::CompileShader(const std::string& FileName, const EShaderType Type) const
{
	using namespace std;

	if (!filesystem::exists(FileName))
	{
		return {};
	}

	ifstream FileStream;
	stringstream TempString;

	try
	{
		FileStream.open(FileName);
		TempString << FileStream.rdbuf();
	}
	catch (...)
	{
		FileStream.close();
		return {};
	}

	const string CodeText = TempString.str();
	const GLchar* CodeStr = CodeText.c_str();
	assert(!CodeText.empty() && "错误，shader code 是空的");
	return CompileShaderFromSrcCode(Type, CodeStr);
}

std::shared_ptr<FShaderProgram> ShaderBuild::Link(const std::optional<GLuint> VertexHandle,
	const std::optional<GLuint> FragmentHandle) const
{
	GLuint ProgramHandle = glCreateProgram();
	glAttachShader(ProgramHandle, VertexHandle.value());
	glAttachShader(ProgramHandle, FragmentHandle.value());
	glLinkProgram(ProgramHandle);

	GLint Code = 0;
	glGetProgramiv(ProgramHandle, GL_LINK_STATUS, &Code);
	if (Code != GL_TRUE)
	{
		PrintProgramLog(ProgramHandle, "链接 FShaderProgram 错误");
		return nullptr;
	}

	glDeleteShader(VertexHandle.value());
	glDeleteShader(FragmentHandle.value());

	return std::make_shared<FShaderProgram>(ProgramHandle);
}

std::shared_ptr<FShaderProgram> ShaderBuild::Build() const
{
	using namespace std;

	const auto VertexHandle = this->CompileShader(VertexFileName.string(), EShaderType::Vertex);
	if (!VertexHandle.has_value())
	{
		return nullptr;
	}

	const auto FragmentHandle = this->CompileShader(FragmentFileName.string(), EShaderType::Fragment);
	if (!FragmentHandle.has_value())
	{
		return nullptr;
	}

	std::shared_ptr<FShaderProgram> Value = Link(VertexHandle, FragmentHandle);
	return Value;
}

std::shared_ptr<FShaderProgram> ShaderBuild::BuildWithSrcCode(const std::string& VSCode, const std::string& FSCode)
{
	using namespace std;

	const auto VertexHandle = this->CompileShaderFromSrcCode(EShaderType::Vertex, VSCode.c_str());
	if (!VertexHandle.has_value())
	{
		return nullptr;
	}

	const auto FragmentHandle = this->CompileShaderFromSrcCode(EShaderType::Fragment, FSCode.c_str());
	if (!FragmentHandle.has_value())
	{
		return nullptr;
	}

	std::shared_ptr<FShaderProgram> Value = Link(VertexHandle, FragmentHandle);
	return Value;
}
